package matchthreegame.extensions;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import matchthreegame.dataBase.DBManagerHelper;
import matchthreegame.gameLogic.GameChain;
import matchthreegame.gameLogic.GameCookie;
import matchthreegame.gameLogic.GameLevel;
import matchthreegame.gameLogic.GameSwap;
import matchthreegame.gameLogic.GameWinConditions;
import matchthreegame.gameLogic.Player;
import matchthreegame.handlers.RematchRequestHandler;
import matchthreegame.handlers.SendSwapRequestHandler;
import matchthreegame.utils.Bot;
import matchthreegame.utils.BotsManager;
import matchthreegame.utils.GameOverInfo;

import com.smartfoxserver.v2.SmartFoxServer;
import com.smartfoxserver.v2.entities.Room;
import com.smartfoxserver.v2.entities.User;
import com.smartfoxserver.v2.entities.Zone;
import com.smartfoxserver.v2.entities.data.SFSArray;
import com.smartfoxserver.v2.entities.data.SFSObject;
import com.smartfoxserver.v2.entities.variables.RoomVariable;
import com.smartfoxserver.v2.entities.variables.SFSRoomVariable;
import com.smartfoxserver.v2.entities.variables.UserVariable;
import com.smartfoxserver.v2.extensions.SFSExtension;

public class MatchThreeGameRoomExtension extends SFSExtension {

	//constants
	public static final String CMD_START_NEW_GAME 			= "SNG";
	public static final String CMD_SEND_SWAP 				= "SS";
	public static final String CMD_MOVE_RESPONSE			= "MR";
	public static final String CMD_REMATCH_GAME				= "RG";
	public static final String CMD_CHANGE_CURRENT_PLAYER	= "ChCuPl";
	public static final String CMD_FORCED_GAME_OVER			= "FGO";
	public static final String COOKIES_SET 					= "CS";
	public static final String ID_OF_CURRENT_PLAYER 		= "IDCP";
	public static final String GAME_WIN_CONDITIONS			= "GWC";
	public static final String FILL_HOLES_COOKIES 			= "FHC";
	public static final String TOP_UP_COOKIES 				= "TUC";
	public static final String MOVES_PARAMS					= "MoPa";
	public static final String SWAP_OBJECT					= "SO";
	public static final String PLAYER_TO_MOVE				= "PTM";
	public static final String FIRST_PLAYER_INFO			= "FPI";
	public static final String SECOND_PLAYER_INFO			= "SPI";
	public static final String IS_GAME_OVER					= "IGO";
	public static final String WINNER_ID					= "WID";
	public static final String HERO_WAVE_INFO				= "HWI";
	public static final String CHOSEN_HERO_TYPE				= "CHT";
	public static final String CHOSEN_HERO_PRODUCES		    = "CHP";
	public static final String CHOSEN_HERO_ATTACK			= "CHA";
	
	
	public static final String SHUFFLE_COOKIES_SET          = "SCS";
	public static final String CURRENT_COOKIES_SET          = "CCS";
	
	// Hero info
	public static final String FIRST_PLAYER_HERO_CONDITIONS 			 = "FPHC";
	public static final String SECOND_PLAYER_HERO_CONDITIONS 		     = "SPHC";
	public static final String FIRST_PLAYER_HERO_INFO      			     = "FPHI";
	public static final String SECOND_PLAYER_HERO_INFO                   = "SPHI";
	public static final String FIRST_PLAYER_HERO_SKILLS_ON_MYSELF        = "FPHSM";
	public static final String FIRST_PLAYER_HERO_SKILLS_ON_OTHER_PLAYER  = "FPHSOP";
	public static final String SECOND_PLAYER_HERO_SKILLS_ON_MYSELF       = "SPHSM";
	public static final String SECOND_PLAYER_HERO_SKILLS_ON_OTHER_PLAYER = "SPHSOP";
	
	public static final int BOT_ID   			  				= -1;
	private static final int DEFAULT_MOVE_TIME   				= 21;
	private static final float ADDITIONAL_DELAY_FOR_ONE_WAVE	= 1.0f;

	private SmartFoxServer sfs;
	//	private ScheduledFuture<?> taskScheduler;
	private ScheduledFuture<?> sendStartGameMessage;
	private ScheduledFuture<?> timerTask;
	private ScheduledFuture<?> botMoveTask;
	private List<User> userList;
	private Random random = new Random();
	private SFSArray moveResponse = new SFSArray();
	private SFSObject swapSentToClient;
	private Boolean isPlayingWithBot;
	GameLevel level;
	Player firstPlayer;
	Player secondPlayer;
	boolean isGameOver;
	int winnerID;
	WeakReference<DBManagerHelper> dbHelper = null;
	private HashMap<Integer, Integer> gameWinConditions;
	private Bot bot;
	private int currentPlayerID;
	private int performedMoves;
	private long startTime;

	@Override
	public void init() {
		// TODO Auto-generated method stub
//		trace("Room extension created");

		addRequestHandler(CMD_SEND_SWAP, SendSwapRequestHandler.class);
		addRequestHandler(CMD_REMATCH_GAME, RematchRequestHandler.class);

		initDBHelper();

		userList = getGameRoom().getUserList();

		firstPlayer = new Player(this);
		secondPlayer = new Player(this);
		isGameOver = false;
		winnerID = 0;
		performedMoves = 0;

		sfs = SmartFoxServer.getInstance();
		sendStartGameMessage = sfs.getTaskScheduler().schedule(new Runnable() {
			@Override
			public void run() {

				startNewGame();

			}
		}, 300, TimeUnit.MILLISECONDS);

		//		taskScheduler = sfs.getTaskScheduler().scheduleAtFixedRate(new BroadcastTask(), DELAY, UPDATE_FREQUENCY, TimeUnit.MILLISECONDS);

	}

	public void startNewGame() {

		try {
			
			userList = getGameRoom().getUserList();
			
			// Set fields from 'user' table, for each User as UserVariables
			updateUserVariables();
			
			//Check if its game against bot
			isPlayingWithBot = getGameRoom().getVariable(MatchThreeGameZoneExtension.IS_PLAYING_WITH_BOT).getBoolValue();

			if ( isPlayingWithBot ) {
				String botName = getGameRoom().getVariable(MatchThreeGameZoneExtension.BOT_NAME).getStringValue();
				this.bot = BotsManager.getBotsManager().getBotByName(botName);
				fetchInfoForBot(botName);
				trace("Bot named: " + this.bot.getBotName());
			}
			trace("Room with " + (isPlayingWithBot ? "BOT" : "PLAYER") );

			//Load level and send info for starting game to players
			MatchThreeGameRoomExtension.this.level = new GameLevel(1, MatchThreeGameRoomExtension.this);
			HashSet<GameCookie> newCookies = MatchThreeGameRoomExtension.this.level.shuffle();

			SFSObject infoForNewGame = createInfoForNewGame(newCookies);
			send(MatchThreeGameRoomExtension.CMD_START_NEW_GAME, infoForNewGame, userList, false);
			trace("Send start game event to " + userList);

			//Start timer
			currentPlayerID = infoForNewGame.getInt(ID_OF_CURRENT_PLAYER);
			startTimer(27);
			
			//Check if bot is first to move and if so - move
			if ( infoForNewGame.getInt(ID_OF_CURRENT_PLAYER) == BOT_ID ) {
				performBotMove(5 + 5);
			}
			
			startTime = System.currentTimeMillis();

		} catch (Exception e) {
			trace(e.getMessage().toString());
		}

	}

	private void initDBHelper() {

		try {
			MatchThreeGameZoneExtension zoneExtension = (MatchThreeGameZoneExtension)getParentRoom().getZone().getExtension();
			dbHelper = new WeakReference<DBManagerHelper>(zoneExtension.getDBHelper());
//			trace("DBHelper created succesfully");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			trace("### DBHelper NOT created ###");
			trace(e.getMessage().toString());
		}

	}


	//Populate stats for choosen bot
	private void fetchInfoForBot(String choosenBotName) {

		List<UserVariable> userVariables = dbHelper.get().getVariablesForUser(choosenBotName);

		this.bot.populateStatsFromUserVariables(userVariables);
		
		// get real player's rank
		int firstPlayerRank = userList.get(0).getVariable(DBManagerHelper.PLAYER_RANK).getIntValue();

		// Play game with EASY bot
		if ( firstPlayerRank <= 1050 ) {

			this.bot.heroProduces = 2;
			this.bot.heroAttack = -1;
			this.bot.heroType = random.nextInt(15) + 1;
			
			trace(userList.get(0).getName() + " rank: " + firstPlayerRank + " => EASY bot");

		} else if ( firstPlayerRank <= 2000 ) {
			
			this.bot.heroProduces = 2 + randomInteger(1, 3);
			this.bot.heroAttack = -1 + (randomInteger(1, 3) * -1);
			this.bot.heroType = random.nextInt(15) + 1;
			
			trace(userList.get(0).getName() + " rank: " + firstPlayerRank + " => MEDIUM bot");
			
		} else {
			
			this.bot.heroProduces = 2 + randomInteger(3, 5);
			this.bot.heroAttack = -1 + (randomInteger(3, 5) * -1);
			this.bot.heroType = random.nextInt(15) + 1;
			
			trace(userList.get(0).getName() + " rank: " + firstPlayerRank + " => HARD bot");
			
		}
		
		setRoomVariablesForBot();
		
	}
	
	public int randomInteger(int min, int max) {

	    Random rand = new Random();

	    // nextInt excludes the top value so we have to add 1 to include the top value
	    int randomNum = rand.nextInt((max - min) + 1) + min;

	    return randomNum;
	}
	
	private void setRoomVariablesForBot() {
		
		ArrayList<RoomVariable> roomVariables = new ArrayList<RoomVariable>();
		roomVariables.add(new SFSRoomVariable(MatchThreeGameZoneExtension.BOT_WINS, this.bot.wins));
		roomVariables.add(new SFSRoomVariable(MatchThreeGameZoneExtension.BOT_LOSSES, this.bot.losses));
		roomVariables.add(new SFSRoomVariable(MatchThreeGameZoneExtension.BOT_RANK, this.bot.rank));
		roomVariables.add(new SFSRoomVariable(MatchThreeGameZoneExtension.BOT_HERO_TYPE, this.bot.heroType));
		roomVariables.add(new SFSRoomVariable(MatchThreeGameZoneExtension.BOT_HERO_PRODUCES, this.bot.heroProduces));
		roomVariables.add(new SFSRoomVariable(MatchThreeGameZoneExtension.BOT_HERO_ATTACK, this.bot.heroAttack));
		
		getApi().setRoomVariables(null, this.getGameRoom(), roomVariables);
		
	}
	

	private void startTimer(float timeForTurn) {
		
		final int playerOnTurn = currentPlayerID;
		
		timerTask = sfs.getTaskScheduler().schedule(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				playerSkippedTurn(playerOnTurn);
			}
		}, (int)timeForTurn, TimeUnit.SECONDS);
		
	}
	
	private void playerSkippedTurn(int playerIDThatSkippedTurn) {
		
		if ( isGameOver == false && shouldForceGameOver() ) {
			trace("Game over from playerSkippedTurn");
			return;
		}
		
		//I am not sure if this check is needed but let it stay for now (if player moved just before this method executed or etc.)
		if ( isPlayingWithBot == false ) {
			if ( playerIDThatSkippedTurn == firstPlayer.playerID ) {
				currentPlayerID = secondPlayer.playerID;
				timerTask.cancel(false);
				startTimer(DEFAULT_MOVE_TIME);
			} else if ( playerIDThatSkippedTurn == secondPlayer.playerID ) {
				currentPlayerID = firstPlayer.playerID;
				timerTask.cancel(false);
				startTimer(DEFAULT_MOVE_TIME);
			}
		} else {
			if ( isGameOver == false ) {
				currentPlayerID = BOT_ID;
				int randomDelay = random.nextInt(3) + 4;
				performBotMove(randomDelay);
			}
		}

		trace("Skipped turn by " + playerIDThatSkippedTurn);
		SFSObject currentPlayerIDInfo = new SFSObject();
		currentPlayerIDInfo.putInt(ID_OF_CURRENT_PLAYER, currentPlayerID);
		send(CMD_CHANGE_CURRENT_PLAYER, currentPlayerIDInfo, getGameRoom().getUserList());
		
	}
	
	
	private SFSObject createInfoForNewGame(HashSet<GameCookie> initalSet) {
		
		SFSObject infoForNewGame = new SFSObject();

		//Add current player, if game with bot possible ids will be idOfPlayer and -1 (-1 used for bot)
		int idOfCurrentPlayer; 
		int randomNum = random.nextInt(2);
		if ( isPlayingWithBot ) {
			idOfCurrentPlayer = randomNum == 0 ? getGameRoom().getUserList().get(0).getPlayerId() : BOT_ID;
		} else {
			idOfCurrentPlayer = getGameRoom().getUserList().get(random.nextInt(2)).getPlayerId();
		}
		
		currentPlayerID = idOfCurrentPlayer;
		infoForNewGame.putInt(ID_OF_CURRENT_PLAYER, idOfCurrentPlayer);
		trace("Id of current player: " + idOfCurrentPlayer);

		//Add game win conditions
		gameWinConditions = GameWinConditions.getWinCondition();
		SFSArray sfsGameWinConditions = SFSArray.newInstance();

		// 6 => number of cookie types
		for (int i = 1; i <= 6; i++) {

			// get the value of the cookieType
			Integer cookieTypeConditionValue = gameWinConditions.get(i);

			// set ConditionValue for key current cookieType ( example: { '1':14, '2':43, '3': 0 ... } ) 
			SFSObject sfsCookieTypeConditionValue = SFSObject.newInstance();
			sfsCookieTypeConditionValue.putInt(Integer.toString(i), cookieTypeConditionValue.intValue());

			sfsGameWinConditions.addSFSObject(sfsCookieTypeConditionValue);
		}

		infoForNewGame.putSFSArray(GAME_WIN_CONDITIONS, sfsGameWinConditions);

		// set Players initial variables
		firstPlayer.playerID = userList.get(0).getPlayerId();
		firstPlayer.id = userList.get(0).getId();
		firstPlayer.name = userList.get(0).getName();
		firstPlayer.gameWinConditions = gameWinConditions;
		
		// set Hero Type from UserVariable
		int firstPlayerHeroType = userList.get(0).getVariable(CHOSEN_HERO_TYPE).getIntValue();
		int firstPlayerHeroProduces = userList.get(0).getVariable(CHOSEN_HERO_PRODUCES).getIntValue();
		int firstPlayerHeroAttack = userList.get(0).getVariable(CHOSEN_HERO_ATTACK).getIntValue();
		trace("firstPlayerHero Type: " + firstPlayerHeroType + 
				" Produces: " + firstPlayerHeroProduces + 
				" Attack: " + firstPlayerHeroAttack);
		
		firstPlayer.initHero(firstPlayerHeroType, firstPlayerHeroProduces, firstPlayerHeroAttack);
		firstPlayer.resetCounters();
		
		if ( isPlayingWithBot ) {
			
			secondPlayer.playerID = BOT_ID;
			secondPlayer.id = BOT_ID;
			secondPlayer.name = this.bot.getBotName();
			secondPlayer.initHero(this.bot.heroType, this.bot.heroProduces, this.bot.heroAttack);
			
		} else {
			
			secondPlayer.playerID = userList.get(1).getPlayerId();
			secondPlayer.id = userList.get(1).getId();
			secondPlayer.name = userList.get(1).getName();
			
			// set Hero Type from UserVariable
			int secondPlayerHeroType = userList.get(1).getVariable(CHOSEN_HERO_TYPE).getIntValue();
			int secondPlayerHeroProduces = userList.get(1).getVariable(CHOSEN_HERO_PRODUCES).getIntValue();
			int secondPlayerHeroAttack = userList.get(1).getVariable(CHOSEN_HERO_ATTACK).getIntValue();
			
			secondPlayer.initHero(secondPlayerHeroType, secondPlayerHeroProduces, secondPlayerHeroAttack);
			
		}
		
		secondPlayer.gameWinConditions = gameWinConditions;
		secondPlayer.resetCounters();
		isGameOver = false;
		winnerID = 0;
		
//		trace("firstPlayerHeroType: " + firstPlayer.hero.type + " secondPlayerHeroType: " +  secondPlayer.hero.type);

		//Add initial cookies set
		SFSArray cookiesSet = new SFSArray();
		for (GameCookie gameCookie : initalSet) {
			SFSObject cookie = gameCookie.asSFSObject();
			cookiesSet.addSFSObject(cookie);
		}
		infoForNewGame.putSFSArray(COOKIES_SET, cookiesSet);

//		// Set fields from 'user' table, for each User as UserVariables
//		updateUserVariables();
		
		// set HeroUsageConditions
		SFSObject firstPlayerHeroUsageConditions = firstPlayer.getHeroUsageConditions();
		infoForNewGame.putSFSObject(FIRST_PLAYER_HERO_CONDITIONS, firstPlayerHeroUsageConditions);
		
		SFSObject secondPlayerHeroUsageConditions = secondPlayer.getHeroUsageConditions();
		infoForNewGame.putSFSObject(SECOND_PLAYER_HERO_CONDITIONS, secondPlayerHeroUsageConditions);
		
		// Set HeroSkills
		SFSObject firstPlayerHeroSkillsOnMyself = firstPlayer.getHeroSkillsOnMyself();
		infoForNewGame.putSFSObject(FIRST_PLAYER_HERO_SKILLS_ON_MYSELF, firstPlayerHeroSkillsOnMyself);
		
		SFSObject firstPlayerHeroSkillsOnOtherPlayer = firstPlayer.getHeroSkillsOnOtherPlayer();
		infoForNewGame.putSFSObject(FIRST_PLAYER_HERO_SKILLS_ON_OTHER_PLAYER, firstPlayerHeroSkillsOnOtherPlayer);
		
		SFSObject secondPlayerHeroSkillsOnMyself = secondPlayer.getHeroSkillsOnMyself();
		infoForNewGame.putSFSObject(SECOND_PLAYER_HERO_SKILLS_ON_MYSELF, secondPlayerHeroSkillsOnMyself);
		
		SFSObject secondPlayerHeroSkillsOnOtherPlayer = secondPlayer.getHeroSkillsOnOtherPlayer();
		infoForNewGame.putSFSObject(SECOND_PLAYER_HERO_SKILLS_ON_OTHER_PLAYER, secondPlayerHeroSkillsOnOtherPlayer);
		
		return infoForNewGame;

	}

	//Perform swap from the received swap object from player
	public void performSwap(GameSwap swap, int senderID) {

		this.swapSentToClient = GameSwap.asSFSObject(swap);

		level.performSwap(swap);
		handleMatches(senderID);

	}

	public void handleMatches(int senderID) {

		HashSet<GameChain> chains = level.removeMatches();
		if ( chains.size() == 0 ) {

			sendMoveResponseToUsers(moveResponse, senderID);

			level.detectPossibleSwaps();

			//If playing with bot and player just moved, perform bot move
			if ( isPlayingWithBot == true && senderID != BOT_ID && isGameOver == false ) {
				//Put 1sec for each wave + normal delay
				int randomDelay = random.nextInt(3) + 4;
				performBotMove(moveResponse.size() + randomDelay);
			}

			moveResponse = new SFSArray();

			performedMoves++;
			
			return;
		}

		// Updates counter values for cookies for sender
		calculateScoreForSenderWithChains(senderID, chains);

		//Put falling cookies and spawned cookies in SFSObject and then put this object in 
		//the movesArray ( because there can be more tan one wave so we keep track of them)
		SFSObject currentWaveObject = new SFSObject();
		
		// Put HeroWaveInfo in each Wave object
		SFSObject heroWaveInfoObject = createHeroWaveInfoObject(senderID);
		currentWaveObject.putSFSObject(HERO_WAVE_INFO, heroWaveInfoObject);
		
		//Falling cookies
		ArrayList<ArrayList<GameCookie>> columns = level.fillHoles();
		SFSArray fillHolesCookies = getSFSArrayForArrayList(columns);
		currentWaveObject.putSFSArray(FILL_HOLES_COOKIES, fillHolesCookies);

		//Spawned cookies
		ArrayList<ArrayList<GameCookie>> cookiesToSpawn = level.topUpCookies();
		SFSArray topUpCookies = getSFSArrayForArrayList(cookiesToSpawn);
		currentWaveObject.putSFSArray(TOP_UP_COOKIES, topUpCookies);

		moveResponse.addSFSObject(currentWaveObject);
//		trace("Added object to waves- " + currentWaveObject + " moveResponse: " + moveResponse);

//		level.printCookies();

		handleMatches(senderID);

	}
	
	
	private SFSObject createHeroWaveInfoObject(int senderID) {
		
		SFSObject heroInfo = null;
		
		// getHeroInfo for sender
		if ( senderID == firstPlayer.playerID ) {
			heroInfo = firstPlayer.getHeroWaveInfoAsSFSObject();
		} else {
			heroInfo = secondPlayer.getHeroWaveInfoAsSFSObject();
		}
		
		return heroInfo;
		
	}
	

	private void performBotMove(int delayBeforeMove) {

		//Make logic to filter bot moves
		GameSwap botMove = null;
//		for (GameSwap swap : level.possibleSwaps) {
//		botMove = swap;
//		break;
//	}
	
	HashMap<Integer, Integer> neededCookies = this.secondPlayer.getNeededCookies(this.gameWinConditions);
	botMove = BotsManager.getBotsManager().getBotMoveFor(neededCookies, this.level.possibleSwaps,
			(bot.heroAttack + bot.heroProduces) / 2, secondPlayer.cookieType5Counter, secondPlayer.cookieType6Counter, bot.botLevel);
	
		//this is needed beacuse cant pass non-final variable to inner class
		final GameSwap botMoveToPass = botMove;
		
		botMoveTask = sfs.getTaskScheduler().schedule(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				if ( isGameOver == false && shouldForceGameOver() ) {
					trace("Game over from perform bot move");
					return;
				}
				trace("Bot should make move !");
				performSwap(botMoveToPass, BOT_ID);
			}
		}, delayBeforeMove, TimeUnit.SECONDS);

	}
	

	private void sendMoveResponseToUsers(SFSArray moveResponse, int senderID) {

		if ( isGameOver == false && shouldForceGameOver() ) {
			trace("Game over from send move response");
			return;
		}
		
		
		//Send response with move object
		SFSObject params = new SFSObject();
		params.putSFSArray(MOVES_PARAMS, moveResponse);

		// set Swap Object
		params.putSFSObject(SWAP_OBJECT, this.swapSentToClient);

		// set player to move playerID and cancel timer task
		int playerToMoveID = getPlayerToMoveID(senderID);
		currentPlayerID = playerToMoveID;
		timerTask.cancel(true);
		startTimer(DEFAULT_MOVE_TIME + moveResponse.size() * ADDITIONAL_DELAY_FOR_ONE_WAVE);
		params.putInt(PLAYER_TO_MOVE, playerToMoveID);

		// set First Player info
		SFSArray firstPlayerInfo = firstPlayer.playerInfoAsSFSArray();
		params.putSFSArray(FIRST_PLAYER_INFO, firstPlayerInfo);
		SFSArray firstPlayerHeroInfo = firstPlayer.getHeroCountersInfoAsSFSArray();
		params.putSFSArray(FIRST_PLAYER_HERO_INFO, firstPlayerHeroInfo);

		// set Second Player info
		SFSArray secondPlayerInfo = secondPlayer.playerInfoAsSFSArray();
		params.putSFSArray(SECOND_PLAYER_INFO, secondPlayerInfo);
		SFSArray secondPlayerHeroInfo = secondPlayer.getHeroCountersInfoAsSFSArray();
		params.putSFSArray(SECOND_PLAYER_HERO_INFO, secondPlayerHeroInfo);

		// set Is Game Over and WinnerID
		checkForGameEnd();
		params.putBool(IS_GAME_OVER, this.isGameOver);
		params.putInt(WINNER_ID, this.winnerID);
			
		SFSArray shuffleCookies = getShuffleCookiesIfNeeded();
		
		if ( shuffleCookies != null && isGameOver == false ) {
			params.putSFSArray(SHUFFLE_COOKIES_SET, shuffleCookies);
		}
		
		// add Current Board
		SFSArray currentCookiesSet = getCurrentBoard();
		params.putSFSArray(CURRENT_COOKIES_SET, currentCookiesSet);

		send(CMD_MOVE_RESPONSE, params, userList, false);
//		trace("Response send " + moveResponse + " size is: " + moveResponse.size());
		
//		trace("### PossibleSwaps: " + this.level.possibleSwaps.size());

	}
	
	private SFSArray getShuffleCookiesIfNeeded() {
		
		SFSArray cookies = null;
		
		if ( this.level.possibleSwaps.size() < 10 ) {
			
			// change possible swaps condition in level.shuffle!
			HashSet<GameCookie> newCookies = MatchThreeGameRoomExtension.this.level.shuffle();
			
			//Add initial cookies set
			cookies = new SFSArray();
			for (GameCookie gameCookie : newCookies) {
				SFSObject cookie = gameCookie.asSFSObject();
				cookies.addSFSObject(cookie);
			}
			
		}
		
		return cookies;
		
	}
	
	
	private SFSArray getCurrentBoard() {
		
		SFSArray cookies = null;
		
		// get Current Board
		HashSet<GameCookie> currentCookies = MatchThreeGameRoomExtension.this.level.getCurrentBoard();

		// Add current cookies set
		cookies = new SFSArray();
		
		for (GameCookie gameCookie : currentCookies) {
			
			SFSObject cookie = gameCookie.asSFSObject();
			cookies.addSFSObject(cookie);
			
		}
		
		return cookies;
		
	}

	
	// Determines when the game should end
	private void checkForGameEnd() {

		if ( firstPlayer.didWin() == true ) {
			handleGameOver(firstPlayer.playerID);
		} else if ( secondPlayer.didWin() == true ) {
			handleGameOver(secondPlayer.playerID);
		}

	}

	private void handleGameOver(int winnerID) {

		if ( botMoveTask != null ) botMoveTask.cancel(true);
		
		timerTask.cancel(true);
		this.isGameOver = true;
		this.winnerID = winnerID;
		updateScoreOnUserTable();
		insertGameOverInfoOnGameTable();

		trace("Game Over! Winner: " + this.winnerID );

	}


	// Make UPDATE on 'user' table for both players
	private void updateScoreOnUserTable() {

		for (int i = 0; i < userList.size(); i++) {
			User user = userList.get(i);
			String userName = user.getName();

			if ( user.getPlayerId() == winnerID ) {
				// UPDATE the Winner fields in 'user' table
				dbHelper.get().updateFieldsForUserWithValues(userName, true);

			} else {
				// UPDATE the Looser fields in 'user' table
				dbHelper.get().updateFieldsForUserWithValues(userName, false);
			}
		}

		//IF playing with bot save its info
		if ( isPlayingWithBot ) {
			if ( BOT_ID == winnerID ) {
				// UPDATE the Winner fields in 'user' table
				dbHelper.get().updateFieldsForUserWithValues(this.bot.getBotName(), true);

			} else {
				// UPDATE the Looser fields in 'user' table
				dbHelper.get().updateFieldsForUserWithValues(this.bot.getBotName(), false);
			}
		}

	}
	
	
	// INSERT new GameOverInfo to 'game' table
	private void insertGameOverInfoOnGameTable() {
		
		String winner;
		
		GameOverInfo gameOverInfo;
		double timeElapsed = calculateElapsedTime();
		
		// playing with bot 
		if ( isPlayingWithBot ) {
			
			if ( BOT_ID == winnerID ) {
				// UPDATE the Winner fields in 'user' table
				winner = this.bot.getBotName();

			} else {
				// First player is the winner
				winner = firstPlayer.name;
				
			}
			
			gameOverInfo = new GameOverInfo(winner, firstPlayer.name, firstPlayer.hero.type,
					firstPlayer.hero.produces, firstPlayer.hero.attack, false,
					bot.getBotName(), bot.heroType, bot.heroProduces, bot.heroAttack, true,
					performedMoves, timeElapsed);
			
		} else {
			// two real players game
			
			if ( firstPlayer.playerID == winnerID ) {
				
				// first player is the winner
				winner = firstPlayer.name;

			} else {
				
				// second player is the winner
				winner = secondPlayer.name;
				
			}
			
			gameOverInfo = new GameOverInfo(winner, firstPlayer.name, firstPlayer.hero.type,
					firstPlayer.hero.produces, firstPlayer.hero.attack, false,
					secondPlayer.name, secondPlayer.hero.type, 
					secondPlayer.hero.produces, secondPlayer.hero.attack, false,
					performedMoves, timeElapsed);
			
		}
		
		dbHelper.get().insertGameOverInfo(gameOverInfo);
		
	}
	
	
	private double calculateElapsedTime() {
		
		long estimatedTime = System.currentTimeMillis() - startTime;
		
		long secondsInMilli = 1000;
		long minutesInMilli = secondsInMilli * 60;

		long elapsedMinutes = estimatedTime / minutesInMilli;
		estimatedTime = estimatedTime % minutesInMilli;
		
		long elapsedSeconds = estimatedTime / secondsInMilli;
		
		String elapsedSecondsString;
		
		if ( elapsedSeconds < 10 ) {
			
			elapsedSecondsString = "0" + String.valueOf(elapsedSeconds);
			
		} else {
			
			elapsedSecondsString = String.valueOf(elapsedSeconds);
			
		}
		
		// Make string from minutes and seconds long numbers
		String timeString = String.valueOf(elapsedMinutes) + "." + elapsedSecondsString;
		
		// Make double from String
		double timeElapsed = Double.valueOf(timeString);
		
		return timeElapsed;
		
	}
	


	// Updates counter values for cookies for sender
	private void calculateScoreForSenderWithChains(int senderID, HashSet<GameChain> chains) {

		if ( senderID == firstPlayer.playerID ) {
			firstPlayer.calculateScoreForChains(chains, firstPlayer, secondPlayer);
		} else {
			secondPlayer.calculateScoreForChains(chains, secondPlayer, firstPlayer);
		}

	}


	private int getPlayerToMoveID(int senderID) {

		int playerToMoveID = 0;

		if ( isPlayingWithBot ) {
			playerToMoveID = senderID == BOT_ID ? userList.get(0).getPlayerId() : BOT_ID;
		} else {
			// playerToMove is the opponent of sender
			for (User user : userList) {
				if ( user.getPlayerId() != senderID ) {
					playerToMoveID = user.getPlayerId();
					break;
				}
			} 
		}

		return playerToMoveID;

	}


	//Helper method to parse ArrayList<ArrayList<GameCookie>> into SFSArray with SFSArrays inside of it
	private SFSArray getSFSArrayForArrayList(ArrayList<ArrayList<GameCookie>> arrayList) {

		SFSArray outerArray = new SFSArray();
		for (ArrayList<GameCookie> columns : arrayList) {
			SFSArray innerArray = new SFSArray();
			for (GameCookie gameCookie : columns) {
				SFSObject cookie = gameCookie.asSFSObject();
				innerArray.addSFSObject(cookie);
			}
			outerArray.addSFSArray(innerArray);
		}

		return outerArray;

	}


	private void updateUserVariables() {

		for (User user : userList) {

			List<UserVariable> variables = null;

			//Here get info from dbHelper class
			variables = dbHelper.get().getVariablesForUser(user.getName());

			// Set the variables
			getApi().setUserVariables(user, variables);

//			trace("updateUserVariables()");
		}

	}
	
	
	public void userLeftTheGame(long userId) {
		
		if ( isGameOver == true ) {
			return;
		}
		
		trace("UserLeftTheGame userId: " + userId);
		
		// First player left the game
		if ( userId == firstPlayer.id ) {
			
			// Second player Win the game
			handleGameOver(secondPlayer.playerID);
			
		} else if ( userId == secondPlayer.id ) {
			
			// Second player left the game, so First player is the Winner
			handleGameOver(firstPlayer.playerID);
			
		} else {
			
			// There is Bug userId is not as expected, Some of the players maybe is disconnected
			
			// refresh userlist	
			userList = getGameRoom().getUserList();
			trace("userlist: " + userList + ", firstPlayerID: " + firstPlayer.playerID + ", secondPlayerID: " + secondPlayer.playerID);
			
			// There is no real Players, so Bot wins
			if ( isPlayingWithBot == true && userList.size() == 0 ) {
				
				handleGameOver(BOT_ID);
				
			} else {
				
				// 1 of the real players left the game
				if ( userList.size() == 1 ) {
					handleGameOver(userList.get(0).getPlayerId());					
				}
				
			}
			
		}
		
		// Send ForcedGameOver event
		if ( isGameOver == true ) {
			sendForcedGameOver();
		}
		
	}
	
	
	//This method is used to check if one of players has left the room.
	//Sometimes even when one player left game game loop keeps running and sending responses to client and 
	//bugs the game. This method tries to fix this problem and its called in various places where server send 
	//responses to client.
	private boolean shouldForceGameOver() {
		
		boolean shouldForceGameOver = false;
		userList = getGameRoom().getUserList();

		// There is no real Players, so Bot wins
		if ( isPlayingWithBot == true && userList.size() == 0 ) {
			handleGameOver(BOT_ID);
			shouldForceGameOver = true;
			sendForcedGameOver();
		} else if ( isPlayingWithBot == false && userList.size() == 1 ){
			// 1 of the real players left the game
			handleGameOver(userList.get(0).getPlayerId());	
			shouldForceGameOver = true;
			sendForcedGameOver();
		}
		
		return shouldForceGameOver;
		
	}
	
	
	private void sendForcedGameOver() {
		
		trace("Send CMD_FORCED_GAME_OVER");
		
		SFSObject params = new SFSObject();
		params.putBool(IS_GAME_OVER, this.isGameOver);
		params.putInt(WINNER_ID, this.winnerID);
		
		send(CMD_FORCED_GAME_OVER, params, userList, false);
		
	}
	

	public Room getGameRoom() {
		return this.getParentRoom();
	}

	public Zone getZone() {
		return this.getParentZone();
	}

	public int getCurrentPlayerID() {
		return currentPlayerID;
	}

	@Override
	public void destroy()  {
		sendStartGameMessage.cancel(true);
		//		taskScheduler.cancel(true);
		super.destroy();
	}

	//	private class BroadcastTask implements Runnable
	//	{
	//		public void run()
	//	    {		
	//			userList = getGameRoom().getUserList();
	//			
	//			// Only send extension responses when the race has begun
	//			if (playersInfo.size() > 0 ) { //&& (!getParentRoom().getVariable(ReservedRoomVariables.RV_GAME_STARTED).isNull()) 		    					
	//				// Add the offset from game creation to now to the object being broadcast
	//				now = new Date();
	//				currentTime = now.getTime() - startTime;
	//				playersInfo.putLong(TIMESTAMP, currentTime);
	//				
	//				// Send object containing all users latest received positions to all users in game room using TCP
	//				send(RaceGameRoomExtension.CMD_RACERSUPDATE, playersInfo,
	//						userList, true);
	//				
	//				// Cancel taskScheduler when room is empty
	//				if (userList.isEmpty())
	//				{
	//					taskScheduler.cancel(true);        			
	//				}
	//			} else {
	//				trace("Error ! Update is not send !! " + playersInfo );
	//			}
	//	    }
	//	}

}
















